# Copyright (c) 2017 Presto Labs Pte. Ltd.
# Author: inkyu

import logging

from coin.base.param_util import to_list
from coin.base.timestamp import get_timestamp
from coin.exchange.util.feed_checker import FeedChecker


class _EventDistributor(object):
  def __init__(self):
    self._subscription = {}

  def subscribe(self, event_type, key, callback):
    if event_type not in self._subscription:
      self._subscription[event_type] = {}
    subscription = self._subscription[event_type]

    for k in to_list(key):
      if k not in subscription:
        subscription[k] = []
      subscription[k].append(callback)

  def publish(self, event_type, key, event):
    if event_type in self._subscription:
      subscription = self._subscription[event_type]
      if key in subscription:
        for callback in subscription[key]:
          callback(event)

  def is_subscribed(self, event_type, key):
    return (event_type in self._subscription and key in self._subscription[event_type])


class WebsocketHandler(object):
  def __init__(self, *, event_dist=None, feed_checker_config=None):
    self._event_dist = event_dist or _EventDistributor()
    self.ready = False
    self._books = {}
    self._trades = {}
    self._feed_checker = None
    self._feed_checker_config = feed_checker_config
    self._book_builder_observer = None
    self._logger = logging.getLogger(__name__)
    self._is_live = False

    if self._feed_checker_config is not None:
      self._feed_checker = FeedChecker(self._feed_checker_config)
    else:
      self._feed_checker = None

  def set_live(self, is_live):
    self._is_live = is_live

  def observe_book_builder(self, book_builder_observer):
    assert book_builder_observer is not None
    if self._book_builder_observer is book_builder_observer:
      return

    assert self._book_builder_observer is None
    self._book_builder_observer = book_builder_observer
    for book_builder in self._books.values():
      book_builder.set_observer(book_builder_observer)

  @property
  def feed_checker(self):
    return self._feed_checker

  def add_check_product(self, product):
    if self._feed_checker:
      self._feed_checker.add_product(product)

  def _on_periodic_check(self, ioloop, period):
    timestamp = get_timestamp()
    self.publish_event('feed_status',
                       'feed_status',
                       self._feed_checker.check_state(timestamp=timestamp))
    ioloop.call_later(period, self._on_periodic_check, ioloop, period)

  def start_periodic_check(self, ioloop, period):
    if self._feed_checker:
      ioloop.call_later(30, self._on_periodic_check, ioloop, period)

  def is_subscribed(self, event_type, key):
    return self._event_dist.is_subscribed(event_type, key)

  def post_subscribe(self, event_type, keys, callback):
    pass

  def subscribe_event(self, event_type, keys, callback):
    assert all([type(key) == str for key in keys])
    self._event_dist.subscribe(event_type, keys, callback)
    self.post_subscribe(event_type, keys, callback)

  def subscribe(self, symbols, callback):
    self.subscribe_event('book', symbols, callback)

  def subscribe_book(self, symbols, callback):
    self.subscribe_event('book', symbols, callback)

  def subscribe_trade(self, symbols, callback):
    self.subscribe_event('trade', symbols, callback)

  def subscribe_feed_status(self, callback):
    self.subscribe_event('feed_status', 'feed_status', callback)

  def subscribe_instrument(self, symbols, callback):
    self.subscribe_event('instrument', symbols, callback)

  def publish_event(self, event_type, key, event):
    try:
      assert type(key) == str, type(key)
      self._event_dist.publish(event_type, key, event)
    except Exception:
      self._logger.exception('Exception raised when publish event. event_type: %s, key: %s',
                             event_type,
                             key)
